iT邦幫忙

2022 iThome 鐵人賽

DAY 14
1

前情提要

「你介紹了兩種不同的模仿方法,所以是有什麼差別?」

「嗯… 你仔細想想,前天的我跟昨天的我有什麼差別?」艾草回。

「嗯….實在想不到有什麼差別耶?」

「你在回想看看外表,一定有差別的!」

「啊!我知道了!!!」

「猜到答案了嗎?」

「雖然你體重大概只有 8g 左右(綠繡眼型態),但我覺得昨天的你看起來比前天的你胖兩倍!!!」

「屁,那只是毛比較膨!」

https://ithelp.ithome.com.tw/upload/images/20220929/20139066PSFKinPa4I.png


jest.fn() 、 spyOn 差異

今天來介紹一下 jest.fn()spyOn 的差異!

首先來看一下範例程式碼,我們有兩個函式,其中 determineHypertension 函式幫我們確認是否有高血壓風險,而 informResult 函式幫忙印出是否有風險,當在執行 informResult 函式時是會透過呼叫 determineHypertension 函式來幫我們確認是否有高血壓。

// 確認是否有高血壓,回傳值為布林值的 true/false
function determineHypertension(systolic, diastolic) {
	// systolic 收縮壓
	// diastolic 舒張壓
  return systolic >= 120 || diastolic >= 80;
}

function informResult(systolic, diastolic) {
  return determineHypertension(systolic, diastolic)
    ? "請小心你有高血壓風險"
    : "你沒有高血壓";
}

jest.fn()

  1. 首先透過 jest.fn() 來模擬 determineHypertension 函式:

    test('Introducing mocks', () => {
    	// 1.模擬
    	const mockFunction = jest.fn();
      determineHypertension = mockFunction;
    });
    
  2. 將函式 mock 起來後可以透過 Jest 提供的方法來設定回傳值為 false ,並宣告變數 result 且賦予 informResult(130, 90) 的回傳值:

    test("Introducing mocks", () => {
      // arrange
      const mockFunction = jest.fn().mockReturnValue(false);
      determineHypertension = mockFunction;
    	// act
    	const result = informResult(130, 90);
    });
    
  3. 斷言結果

    test("Introducing mocks", () => {
      // arrange
      const mockFunction = jest.fn().mockReturnValue(false);
      determineHypertension = mockFunction;
      // act
      const result = informResult(130, 90);
      // assert
      expect(result).toBe("你沒有高血壓");
      expect(mockFunction).toHaveBeenCalledTimes(1);
    });
    

斷言結果這個步驟會發生一件事,透過 informResult 傳入的收縮壓與舒張壓皆超出高血壓標準,但結果還是印出你沒有高血壓。

這是因為當我們透過 jest.fn() 來模擬函式時它並不會去執行 determineHypertension 函式,而是直接拋出我們設定的回傳值 false

spyOn

接下來讓我們用 spyOn 來嘗試,首先修改一下範例函式,基本邏輯的地方沒有變化,主要是將 determineHypertension 函式包入 object 內,並於呼叫時加上 object. ,方便 spyOn 測試使用:

const object = {
  determineHypertension: (systolic, diastolic) => {
    return systolic >= 120 || diastolic >= 80;
  },
};
function informResult(systolic, diastolic) {
  return object.determineHypertension(systolic, diastolic)
    ? "請小心你有高血壓風險"
    : "你沒有高血壓";
}
  1. 首先先透過 jest.spyOn 來模擬 determineHypertension 函式:

    test("Introducing spyOn", () => {
      // arrange
      const spyOnFunction = jest.spyOn(object, "determineHypertension");
    });
    
  2. 接下來如同上方步驟二一樣宣告變數 result 並賦予值為 informResult(130, 90)

    test("Introducing spyOn", () => {
      // arrange
      const spyOnFunction = jest.spyOn(object, "determineHypertension");
      // act
      const result = informResult(130, 90);
    });
    
  3. 最後這個步驟如果跟上方 jest.fn() 用一樣的方法去斷言你沒有高血壓:

    test("Introducing spyOn", () => {
      // arrange
      const spyOnFunction = jest.spyOn(object, "determineHypertension");
      // act
      const result = informResult(130, 90);
      // assert
      expect(result).toBe("你沒有高血壓");
      expect(spyOnFunction).toHaveBeenCalledTimes(1);
    });
    

    會發現測試不通過:

    https://ithelp.ithome.com.tw/upload/images/20220929/20139066aaMqQSq60e.png

結論

jest.fn() 並不會實際去執行被 mock 的函式,而 spyOn 會去執行傳入的函式。

透過 jest.fn() 可以將 determineHypertensioninformResult 的依賴完全切分開,單獨只測試 informResult 針對 true / false 的回傳值是否正確, spyOn 會去執行該函式。

不過 spyOn 也可以進一步使用 mock 方法如:mockImplementation 等去模擬回傳值或回傳函式,在這種情況下如果希望再次調用原始函式而非僅回傳模擬值,可以透過 mockRestore() 恢復成原始函式。


參考文章

https://jestjs.io/docs/jest-object#jestspyonobject-methodname
https://medium.com/enjoy-life-enjoy-coding/jest-jojo是你-我的替身能力是-mock-4de73596ea6e
https://www.youtube.com/watch?v=1Xafx6o82Aw&ab_channel=SoftwareTestingHelp
https://dwatow.github.io/2020/04-24-jest/jest-doc-5/


上一篇
透過 spyOn 改寫模擬函式
下一篇
處理非同步事件
系列文
<< 測試魔法 >> 這能動嗎?不然就測測看好了!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
碼農
iT邦新手 4 級 ‧ 2024-03-26 10:34:32

jest.fn() 並不會實際去執行被 mock 的函式,而 spyOn 會去執行傳入的函式

請問有建議用哪一種嗎?

個人疑問~

  1. 若沒有用spyOn 去實際觸發函式是不是就沒有測試意義呢?
  2. 用jest.fn() 模擬回傳值,這樣傳入的函式若有修改是不是就不會知道,這樣是不是就少了測試保護的用意?

我要留言

立即登入留言